package com.hu.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.hu.domain.User;
import com.hu.dto.ExportExcelColumnDTO;
import com.hu.dto.ExportExcelDTO;
import com.hu.dto.QueryUserPageDTO;
import com.hu.handler.ExcelHeaderHandler;
import com.hu.strategy.ExcelCellAutoWidthStrategy;
import com.hu.util.SpringUtil;
import com.hu.util.redis.RequestLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 统一excel控制器
 * @author author
 * @date 2024-07-30 9:53
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {

    private static final Logger logger = LoggerFactory.getLogger(ExcelController.class);

    @Resource
    private RequestMappingHandlerMapping handlerMapping;

    /**
     * 记录请求的URI和JAVA方法的映射关系
     */
    private Map<String, Method> uriMethodMap = new HashMap<>();

    @PostConstruct
    public void init(){
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k,v) -> {
            PathPatternsRequestCondition pathPatternsCondition = k.getPathPatternsCondition();
            Set<PathPattern> patterns = pathPatternsCondition.getPatterns();
            uriMethodMap.put(patterns.stream().findFirst().get().getPatternString(), v.getMethod());
        });
    }

    /**
     * 模拟分页数据
     * @return
     */
    @RequestLock(prefix = "testPage")
    @PostMapping("/testPage")
    public Map<String, Object> testPage(@RequestBody QueryUserPageDTO queryUserPageDTO) {
        logger.info("请求参数：{}", queryUserPageDTO);
        Map<String, Object> map = new HashMap();
        map.put("code", 200);
        map.put("msg", "success");
        map.put("page", 1);
        map.put("size", 10);
        map.put("total", 11);
        List<User> dataList = new ArrayList<>();
        if (queryUserPageDTO.getPage() == 1) {
            for (int i = 1; i < 11; i++) {
                User u = new User();
                u.setId(i);
                u.setName("张玉" + i);
                dataList.add(u);
            }
        } else {
            User u = new User();
            u.setId(11);
            u.setName("张玉" + 11);
            // 手动处理每60个字符加一个换行符
            u.setRemark("刚送完了上午的快递，在路边摊吃了碗凉皮，听到这首歌，莫名的心酸，突然好想回家。上了20多年的学，也给不了她想要的，6年的感情抵不过一句“这房子我买不起”。".replaceAll("(.{60})", "$1\n"));
            dataList.add(u);
            map.put("page", 2);
        }
        map.put("data", dataList);
        return map;
    }

    @GetMapping("/export")
    public String hello(HttpServletResponse response) throws Exception {
        // TODO 构建测试数据，应该作为请求参数，这里为了方便测试
        ExportExcelDTO exportExcelDTO = buildExportExcelDTO();
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        // 将日期时间格式化为字符串
        String datetimeString = LocalDateTime.now().format(formatter);
        // 设置响应格式
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(exportExcelDTO.getSheetName(), "UTF-8") + datetimeString + ".xls");
        logger.info("请求参数：{}", exportExcelDTO);
        // 查询分页的方法
        Method method = uriMethodMap.get(exportExcelDTO.getPageQueryUri());
        if (method == null) {
            return "找不到分页查询方法";
        }
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {
            // 设置表头数据
            List<List<String>> headers = new ArrayList<>();
            // 设置表头字体颜色
            Map<String, String> headerColorMap = new HashMap<>();
            for (ExportExcelColumnDTO columnDTO : exportExcelDTO.getColumnList()) {
                headers.add(Arrays.asList(columnDTO.getColumnValue()));
                headerColorMap.put(columnDTO.getColumnValue(), columnDTO.getHeaderColor());
            }
            List<String> filedNameList = exportExcelDTO.getColumnList().stream().map(ExportExcelColumnDTO::getColumnKey).collect(Collectors.toList());
            exportExcelDTO.getColumnList().stream().map(ExportExcelColumnDTO::getColumnKey).collect(Collectors.toList());
            // 构建sheet对象，动态设置表头、列宽等属性，TODO 列宽通过 registerWriteHandler 方法设置
            WriteSheet writeSheet = EasyExcel.writerSheet(exportExcelDTO.getSheetName()).head(headers).includeColumnFieldNames(filedNameList)
                    .registerWriteHandler(new ExcelCellAutoWidthStrategy())
                    .registerWriteHandler(new ExcelHeaderHandler(headerColorMap))
                    .build();

            Object bean = SpringUtil.getBean(Class.forName(method.getDeclaringClass().getName()));
            // 构建分页请求参数
            QueryUserPageDTO queryUserPageDTO = new QueryUserPageDTO();
            Map<String, Object> pageResult = (Map<String, Object>)method.invoke(bean, queryUserPageDTO);
            logger.info("分页查询结果：{}", pageResult);
            this.writeData(pageResult, filedNameList, excelWriter, writeSheet);
            Integer total = (Integer)pageResult.get("total");
            if (total > queryUserPageDTO.getSize()) {
                // 说明还有数据，继续分页查询
                // 计算总页数
                int totalPage = (total + queryUserPageDTO.getSize() - 1) / queryUserPageDTO.getSize();
                for (int i = 2; i <= totalPage; i++) {
                    queryUserPageDTO.setPage(i);
                    pageResult = (Map<String, Object>)method.invoke(bean, queryUserPageDTO);
                    logger.info("分页查询结果：{}", pageResult);
                    this.writeData(pageResult, filedNameList, excelWriter, writeSheet);
                }
            }
        } catch (Exception e) {
            logger.error("导出excel报错", e);
        }
        return "hello";
    }

    private void writeData(Map<String, Object> pageResult, List<String> filedNameList, ExcelWriter excelWriter, WriteSheet writeSheet) {
        Object pageData = pageResult.get("data");
        if (pageData == null) {
            return;
        }
        List<List<Object>> dataList = new ArrayList<>();
        try {
            for (Object o : (List) pageData) {
                Class<?> realClz = o.getClass();
                List<Object> data = new ArrayList<>();
                for (String fieldName : filedNameList) {
                    Field declaredField = realClz.getDeclaredField(fieldName);
                    declaredField.setAccessible(true);
                    data.add(declaredField.get(o));
                }
                dataList.add(data);
            }
            excelWriter.write(dataList, writeSheet);
        } catch (Exception e) {
            logger.error("写数据失败", e);
        }
    }

    private ExportExcelDTO buildExportExcelDTO() {
        ExportExcelDTO exportExcelDTO = new ExportExcelDTO();
        exportExcelDTO.setPageQueryUri("/excel/testPage");
        exportExcelDTO.setSheetName("导出测试文件");
        exportExcelDTO.setColumnList(Arrays.asList(
                new ExportExcelColumnDTO("id", "ID", 10),
                new ExportExcelColumnDTO("name", "姓名", 20, "red"),
                new ExportExcelColumnDTO("remark", "我是一个超级无敌长的备注", 0)
        ));
        return exportExcelDTO;
    }

}
